/**
 * 
 * @TODO Get all data every request (adc and acc)
 * 
 * @TODO Set smooth and shake to unique board
 * 
 * @TODO Make sure that you can ask more than once on each data set
 * 
 * @TODO Null values needs to be eliminated. Maybe most methods should be event based.
 *  
 * @TODO If they are not used in the main app, then they will be deactivated.
 * 
 * @TODO Make sure every call to getDULData is holding all data (both acc and adc)
 * 
 * 
 */

package dulRadio;

import java.lang.reflect.Method;
import processing.core.*;
import processing.serial.*;

public class DULRadio {

	private PApplet parent;
	private Object[] res = null;
	private Serial serial;
	private SerialProxy serialProxy;
	private Method shaking;
	private Board[] sensorboards= new Board[15];

	/**
	 * Initialize interaction with DUL Radio
	 * 
	 * @param parent The Processing sketch (PApplet)
	 * @param comport The COM port the USB stick is connected to
	 * @param baud The Baud rate (defaults to 9600)
	 * @param del A delimiter for when to stop buffering and send to main sketch (defaults to 13)
	 */
	public DULRadio(PApplet parent, String comport, int baud, int del) {
		this.parent = parent;
		this.serialProxy = new SerialProxy();
		this.serial = new Serial(serialProxy, comport, baud);
		serial.setDTR(true);
		serial.bufferUntil(del);
		
		for(int i=0;i<15;i++){
			sensorboards[i]=new Board(i+1);
		}

		parent.registerDispose(this);

		try {
			shaking = parent.getClass().getMethod("shake",
					new Class[] { DULRadio.class });
		} catch (Exception e) {
		}
	}

	/**
	 * Simple initialize
	 */
	public DULRadio(PApplet parent, String comport) {
		this(parent, comport, 9600, 13);
	}

	/**
	 * We need a class descended from PApplet so that we can override the
	 * serialEvent() method to capture serial data. We can't use the Arduino
	 * class itself, because PApplet defines a list() method that couldn't be
	 * overridden by the static list() method we use to return the available
	 * serial ports. This class needs to be public so that the Serial class can
	 * access its serialEvent() method.
	 */
	public class SerialProxy extends PApplet {

		private static final long serialVersionUID = 1L;

		/**
		 * Create the container for the registered dispose() methods, so that
		 * our Serial instance can register its dispose() method (which it does
		 * automatically).
		 */
		public SerialProxy() {
			disposeMethods = new RegisteredMethods();
		}

		/**
		 * Event called when there is serial data
		 * 
		 * @param which Incoming data from serial port
		 */
		public void serialEvent(Serial which) {
			while (which.available() > 0) {
			    String inBuffer = which.readString();   
			    //Create regex to make initial filter
			    //id <,> packettype <,> data1 <,> data2 <,> ... <,> dataN <LF>
			    if (inBuffer != null) {
			    	res = formatDULData(inBuffer);
			    	if(res!=null){
			    		sensorboards[(Integer)res[0]-1].addData(res);
						if (res.length == 5){
							if(sensorboards[(Integer)res[0]-1].isShaking())
								shakeEvent();
						}
			    	}
			    }
			  }
		}
	}

	/**
	 * Closes the serial connection when the parent PApplet closes
	 */
	public void dispose() {
		this.serial.dispose();
	}

	/**
	 * Fire shake event to PApplet
	 */
	private void shakeEvent() {
		if (shaking != null) {
			try {
				shaking.invoke(parent, new Object[] { this });
			} catch (Exception e) {
				System.err.println("Disabling shake() because of an error.");
				e.printStackTrace();
				shaking = null;
			}
		}
	}

	/**
	 * Formatting serial data retrieved from DUL Radio. Returns the integer
	 * values from ACC or ADC. To check whether result is ADC(4) or ACC(5),
	 * check length of result.
	 * 
	 * Messages are last in array. Can be off,
	 * inactivity, sleeping, running, movement, resting.
	 * 
	 * THIS METHODC WILL BE REFACTORED
	 */
	private Object[] formatDULData(String str) {
		Object[] result = null;
		String delims = "[:(,) ]+";
		String[] tokens = str.split(delims);
		// ADC
		if (tokens.length == 5) {
			result = new Object[4];
			result[0] = Integer.parseInt(tokens[0], 16);
			result[1] = Integer.parseInt(tokens[2], 16);
			result[2] = Integer.parseInt(tokens[3], 16);
			result[3] = tokens[4];
		}
		// ACC
		if (tokens.length == 6) {
			result = new Object[5];
			result[0] = Integer.parseInt(tokens[0], 16);
			result[1] = Integer.parseInt(tokens[2], 16);
			result[2] = Integer.parseInt(tokens[3], 16);
			result[3] = Integer.parseInt(tokens[4], 16);
			result[4] = tokens[5];
		}
		return result;
	}
	
	/**********************************/
	/* Methods to use from Processing */
	/**********************************/
	
	
	/**
	 * 
	 * @param id the id of the sensor board to get data from
	 * @return the last received data form sensor board with the id
	 */
	public Integer[] getCurrentData(int id){
		return sensorboards[id-1].getCurrentData();
	}
	
	/**
	 * 
	 * @param id the id of the sensor board to get data from
	 * @return The state of the sensor board (active, inactive, resting sleeping etc.)
	 */
	public String getState(int id){
		return sensorboards[id-1].getState();
	}
	
	
	/**
	 * Configure when a shake is registered
	 * 
	 * @param id the id of the sensor board to get data from
	 * @param minForce The minimum force to consider
	 * @param minDirChange Minimum times in a shake gesture that the direction of movement needs to change.
	 * @param maxPausBtwnDirChg Maximum pause between movements.
	 * @param maxTotDurShk Maximum allowed time for shake gesture.
	 */
	public void configShakeEvent(int id, int minForce, int minDirChange, int maxPausBtwnDirChg, int maxTotDurShk){
		sensorboards[id-1].configShakeEvent(minForce, minDirChange, maxPausBtwnDirChg, maxTotDurShk);
	}
	
	/**
	 * 
	 * @param id the id of the sensor board to get data from
	 * @param s number of samples to smooth over
	 */
	public void setSmoothSamples(int id, int s){
		sensorboards[id-1].setSmoothSamples(s);
	}
	
	/**
	 * 
	 * @param id the id of the sensor board to get data from
	 * @return A list of smoothed values from the sensor board with this id
	 */
	public int[] smoothValues(int id){
		return sensorboards[id-1].smoothValues();
	}
	
	/**
	 * 
	 * @param id the id of the sensor board to get data from
	 * @return 360 degree position for x (0) and z (2). Y (1) is 180 degree. Y is experimental!
	 */
	public int[] getTiltPosition(int id){
		return sensorboards[id-1].getTiltPosition();
	}
	
}